package com.tuu.utils;

import com.tuu.configBean.RedisUtil;
import com.tuu.exception.BizException;
import com.tuu.pojo.JwtClainsObject;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultClock;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
@Slf4j
public class JwtTokenUtil222Origin {
    @Autowired
    private static RedisUtil redisUtil;

    @Autowired
    public void setRedisService(RedisUtil redisUtil) {

        JwtTokenUtil222Origin.redisUtil = redisUtil;

    }

    private static String secret;

    private static Long expiration;
    private static Long userExpiration;
    private static String userIdent;
    @Value("${jwt.secretKey}")
    public void setSecret(String secret) {
        JwtTokenUtil222Origin.secret = secret;
    }

    @Value("${jwt.expiration}")
    public void setExpiration(Long expiration) {
        JwtTokenUtil222Origin.expiration = expiration;
    }
    @Value("${jwt.userVersionExpiration}")
    public void setUserExpiration(Long userExpiration) {
        JwtTokenUtil222Origin.userExpiration = userExpiration;
    }

    @Value("${jwt.userIdentifier}")
    public void setUserIdent(String userIdent) {
        JwtTokenUtil222Origin.userIdent = userIdent;
    }



    private static Clock clock = DefaultClock.INSTANCE;

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }

    public static String generateToken2(JwtClainsObject jco) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken2(jco);
    }

    private static String doGenerateToken2(JwtClainsObject jco) {

        Map<String, Object> claims = new HashMap<String, Object>();//创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        claims.put("uid", jco.getSubjectUid());
        claims.put("user_name", jco.getUsername());
        claims.put(userIdent, jco.getRandomId());
       final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(jco.getSubjectUid())
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS256, generalKey())
                .compact();
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS256, generalKey())
                .compact();
    }

    private static Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration);
    }

    public Boolean validateToken(String jwt, HttpServletRequest request) throws Exception {
        //设置需要解析的jwt
        Date exp = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss");
        //long nowMillis = System.currentTimeMillis();
        String ip = IPAdressUtils.getIpAddress(request);
        System.out.println("ipadress:" + ip);
        BrowerUtils bu = BrowerUtils.getBrowerInfo(request);
        Map<String, Object> map = new HashMap<>();
        Claims claims = null;
        try {
            claims = getAllClaimsFromToken(jwt);
            exp = claims.getExpiration();
            System.out.println("过期时间" + sdf.format(claims.getExpiration()));
            if (!ip.equals(claims.get("ip_address"))) {
                System.out.println("ip地址不正确");
                throw new BizException("-1", "token错误，请重新登陆");
            }
            String browerCont = bu.getBrowserName() + bu.getOperatingSystem() + bu.getBrowserVersion();
            if (!browerCont.equals(claims.get("brower_contact"))) {
                System.out.println("brower_contact不正确");
                throw new BizException("-1", "设备错误，请重新登陆");
            }

        } catch (ExpiredJwtException e) {
            System.out.println("抛出token过期错误");
            System.out.println("过期时间" + sdf.format(e.getClaims().getExpiration()));

            String uidContact = "uid" + e.getClaims().getSubject() + "token";
            getRedisAndDelayToken(uidContact, jwt);
            System.out.println("token在redis未过期，已重新刷新token");
            return true;
        } catch (Exception e) {
            throw new BizException("400", "token流程异常，请稍后重试");
        }
        String uidContact = "uid" + claims.getSubject() + "token";
        getRedisAndDelayToken(uidContact, jwt);
        System.out.println("验证成功。。。");
        return true;
    }

    public Map validateTokenAndCookie(String jwt, HttpServletRequest request) throws Exception {

        Map<String, Object> map = new HashMap<>();
        if (pureValidateToken(jwt, request)) {
            Claims claims = getClaims(jwt);
            map.put("nickname", claims.get("user_name"));
            System.out.println("验证成功。。。返回cookies");
            return map;
        }
        return null;
    }

    //不返回cookie  单纯检验token
    public Boolean pureValidateToken(String jwt, HttpServletRequest request) {
        //设置需要解析的jwt
        //Date exp = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss");
        //long nowMillis = System.currentTimeMillis();
        //String ip = IPAdressUtils.getIpAddress(request);
        //System.out.println("ipadress:" + ip);
        //BrowerUtils bu = BrowerUtils.getBrowerInfo(request);
        Map<String, Object> map = new HashMap<>();
        Claims claims = null;
       String uiden= CookieUtils.getCookie(request,userIdent);
        System.out.println("uident cookie---"+uiden);
       try {
            System.out.println("进入token验证。。。");
            claims = getClaims(jwt);
            //exp = claims.getExpiration();
            System.out.println("过期时间" + sdf.format(claims.getExpiration()));
            if (!uiden.equals(claims.get(userIdent))) {
                System.out.println("userIdent不正确。。。"+claims.get(userIdent));
                return false;
            }
            System.out.println("userIdent正确..."+claims.get(userIdent));

            String uidContact = "uid" + claims.getSubject() + "token";
            try {
                getRedisAndDelayToken(uidContact, jwt);
                return true;
            } catch (Exception ee) {
                throw new BizException("500", "缓存服务器错误，请稍后重试");

            }
        } catch (ExpiredJwtException e) {
            System.out.println("抛出token过期错误");
            System.out.println("过期时间" + sdf.format(e.getClaims().getExpiration()));

            String uidContact = "uid" + e.getClaims().getSubject() + "token";


            try {
                getRedisAndDelayToken(uidContact, jwt);
                return true;
            } catch (Exception ee) {
                throw new BizException("500", "缓存服务器错误，请稍后重试");

            }
//            if(getRedisToken(uidContact,jwt)){
//
//                return true;
//            }else{
//                return false;
//            }


        } catch (Exception e) {
            return false;
        }


    }
    /*
    public Boolean validateToken(String token, UserDetails userDetails) {
        SecurityUserDetails user = (SecurityUserDetails) userDetails;
        final String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername())
                && !isTokenExpired(token)
        );
    }
     */

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Claims getClaims(String token) {
        Claims claims = null;
        try {
            claims = getAllClaimsFromToken(token);
        } catch (ExpiredJwtException e) {
            log.info("getClaimFromToken进入过期错误 使用e.getClaims()");
            claims = e.getClaims();
        } catch (Exception ee) {
            log.error("getClaims错误：" + ee.getMessage());
        }
        return claims;
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {


        Claims claims = null;

        try {
            claims = getAllClaimsFromToken(token);
        } catch (ExpiredJwtException e) {
            log.info("getClaimFromToken进入过期错误 使用e.getClaims()");
            claims = e.getClaims();
        } catch (Exception ee) {
            log.error(ee.getMessage());
        }
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        System.out.println("进入getAllClaimsFromToken");
        return Jwts.parser()
                .setSigningKey(generalKey())
                .parseClaimsJws(token)
                .getBody();
    }


    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    //token没过期 就重新刷新redis token的时间
    public static void getRedisAndDelayToken(String uidContact, String jwt) throws Exception {
        try {
            String redisToken = (String) redisUtil.get(uidContact);
            if (redisToken != null && redisToken.equals(jwt)) {
                //重新设置过期时间5分钟
                //redisUtil.expire(uidContact,Long.parseLong(""+Constants.JwtConst.EXPMILLIS.getTimee()/1000L));
                redisUtil.expire(uidContact, Long.parseLong("" + expiration / 1000L));

                uidContact= uidContact.replace("uid","");
                String uid=uidContact.replace("token","");
                redisUtil.expire("uid"+uid+"pVersion",Long.parseLong("" + userExpiration / 1000L));

                System.out.println("刷新redisToken成功");
            } else {
                System.out.println("token失效，请......");
                throw new BizException("-1", "token不正确，请重新登陆");
            }
        } catch (BizException e) {
            throw new BizException(e.getErrorCode(), e.getErrorMsg());
        } catch (Exception e) {
            throw new BizException("500", "缓存服务器错误，请稍后重试getRedisAndDelayToken");
        }
    }

    public static boolean getRedisToken(String uidContact, String jwt) {
        try {
            String redisToken = (String) redisUtil.get(uidContact);
            if (redisToken != null && redisToken.equals(jwt)) {
                System.out.println("token在redis可以拿到");
                return true;
            } else {
                System.out.println("token在redis未拿到");
                return false;
            }
        } catch (Exception e) {
            System.out.println("token在redis未拿到");
            return false;
        }
    }

    public static SecretKey generalKey() {
        System.out.println("SecretKey中的key是" + secret);
        //String stringKey = Constant.JWT_SECRET;//本地配置文件中加密的密文7786df7fc3a34e26a61c034d5ec8245d
        //String stringKey = "8899";//本地配置文件中加密的密文7786df7fc3a34e26a61c034d5ec8245d
        byte[] encodedKey = Base64.decodeBase64(secret);//本地的密码解码[B@152f6e2

        //System.out.println(encodedKey);//[B@152f6e2
        //System.out.println(Base64.encodeBase64URLSafeString(encodedKey));//7786df7fc3a34e26a61c034d5ec8245d
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
        return key;
    }

    public void logoutJWT(String jwt, HttpServletRequest request) {
        //pureValidateToken(jwt,request)?redisUtil.del("uid"+getUsernameFromToken(jwt)+"token"):"2";
        if (pureValidateToken(jwt, request)) {
            log.info("logoutJWT 有效");
            String uid = getUsernameFromToken(jwt);
            try {
                redisUtil.del("uid" + uid + "token");
                redisUtil.del("uid" + uid + "auths");
                redisUtil.del("uid" + uid + "pVersion");
            } catch (Exception e) {
                throw new BizException("500", "缓存服务器错误，请稍后重试logoutJWT");
            }
        }
    }
}
